# -*- coding:utf-8 -*- 

import struct

import gevent
import gevent.event
import gevent.server
import gevent.queue
import gevent.socket

import google.protobuf.service
import message

from . import rpc_pb2 as rpc
from .utils import Poolable, Error
from .void_pb2 import Void
from .profile import profile_callmethod, set_profile, get_profile

__all__ = [
    'Channel',
    ]

MAX_ID = 1L << 63
LEN_HEADER = '!I'
LEN_HEADER_SIZE = struct.calcsize(LEN_HEADER)

_id = 0
def _next_id():
    global _id
    _id += 1
    if _id >= MAX_ID:
        _id = 1
    return _id


class Controller(google.protobuf.service.RpcController):
    def __init__(self, channel, *a, **kw):
        google.protobuf.service.RpcController.__init__(self, *a, **kw)
        self.channel = channel

    def Reset(self):
        pass

    def Failed(self):
        pass

    def ErrorText(self):
        pass

    def StartCancel(self):
        pass

    def SetFailed(self, reason):
        pass

    def IsCancelled(self):
        pass

    def NotifyOnCancel(self, callback):
        pass

class Callback(object):
    def __init__(self):
        self.response = None

    def __call__(self, resp):
        self.response = resp

class Sender(object):
    def __init__(self, channel):
        self._queue = gevent.queue.Queue()
        self._channel = channel

    def put(self, buff):
        self._queue.put(buff)

    def loop(self):
        while True:
            buff = self._queue.get()
            packed_buff_len = struct.pack(LEN_HEADER, len(buff))
            try:
                result = self._channel._socket.sendall(packed_buff_len + buff)
            except Exception:
                break
            if result is not None:
                break

class Protocol(object):
    class NoBuff(StandardError):
        pass

    def __init__(self):
        self.buff = ''
        self.need = 0
        
    def append_data(self, buff):
#        print 'Protocol get buff: ', repr(buff)
        self.buff += buff

    def process(self):
        while True:
            if self.need <= 0:
                try:
                    self._read_need()
                except Protocol.NoBuff:
#                    print 'read need error.'
                    return ''
            if len(self.buff) < self.need:
#                print 'no more buff.', repr(self.buff)
                return ''
            packet, self.buff = self.buff[:self.need], self.buff[self.need:]
            self.need = 0
#            print 'packet:', repr(packet)
            return packet
    
    def _read_need(self):
        if len(self.buff) < LEN_HEADER_SIZE:
            raise Protocol.NoBuff()
        need, self.buff = self.buff[:LEN_HEADER_SIZE], self.buff[LEN_HEADER_SIZE:]
        self.need, = struct.unpack(LEN_HEADER, need)
#        print 'need:', self.need

class Channel(google.protobuf.service.RpcChannel):
    def __init__(self, transport, service = None):
        google.protobuf.service.RpcChannel.__init__(self)
        self._transport = transport
        self._transport.sub(self._transport.TRANSPORT_DATA,
                self._transport_data_handler)
        self._service = service
        self._pending = {}

        self._protocol = Protocol()

    def __str__(self):
        return '<%s.%s: on %s>' % (
            self.__class__.__module__,
            self.__class__.__name__,
            str(self._transport))

    def __repr__(self):
        return '<%s at %s>' % (str(self), hex(id(self)))

    def _transport_data_handler(self):
#        for packet in self._protocol.process(self._transport.recv()):
#            self._spawn(self._process_packet, packet)
        self._protocol.append_data(self._transport.recv())
        while True:
            packet = self._protocol.process()
            if not packet:
                break
            gevent.spawn(self._process_packet, packet)
    
    def _process_packet(self, buff):
        packet = rpc.Packet()
        packet.ParseFromString(buff)
        if packet.type == rpc.REQUEST:
            self._process_request(packet.identify, packet.serialized)
        elif packet.type == rpc.RESPONSE:
            self._process_response(packet.identify, packet.serialized)
        elif packet.type == rpc.ERROR:
            error = rpc.Error()
            error.ParseFromString(packet.serialized)
            self._process_error(packet.identify, error.info)
        else:
            self._process_error(packet.identify, 'unsupported packet:%s' % repr(packet))

    def _send_error(self, identify, error_info):
        error = rpc.Error()
        error.info = error_info
        packet = rpc.Packet()
        packet.type = rpc.ERROR
        packet.identify = identify
        packet.serialized = error.SerializeToString()
        self._send(packet.SerializeToString())

    def _process_request(self, identify, buff):
        req = rpc.Request()
        req.ParseFromString(buff)
        
        service = self._service

        method = service.GetDescriptor().FindMethodByName(req.method)

        if not method:
            self._send_error(identify, u'method(%s) is not found.' % (req.method))
            return

        if req.HasField('request'):
            request = service.GetRequestClass(method)()
            request.ParseFromString(req.request)
        else:
            request = None
        controller = Controller(self)
        callback = Callback()
        try:
            service.CallMethod(method, controller, request, callback)
        except Exception, e:
            self._send_error(identify, u'some error occurred while call method(%s.%s): %s' % (
                service.GetDescriptor().name, req.method, e))
            raise

        resp = rpc.Response()
        if callback.response:
            resp.response = callback.response.SerializeToString()
        else:
            resp_cls = service.GetResponseClass(method)
            if resp_cls == Void:
                return
            else:
                e = u'method(%s) return None.' % (req.method)
                self._send_error(identify, e)
                return

        packet = rpc.Packet()
        packet.type = rpc.RESPONSE
        packet.identify = identify
        packet.serialized = resp.SerializeToString()
        self._send(packet.SerializeToString())

    def _process_response(self, identify, buff):
        try:
            result = self._pending[identify]
        except KeyError:
            return
        resp = rpc.Response()
        resp.ParseFromString(buff)
        result.set(resp.response)

    def _process_error(self, identify, error_info):
        try:
            result = self._pending[identify]
        except KeyError:
            return
        result.set_exception(Error(error_info))

    def CallMethod(self, mdesc, controller, request, response_class, done = None):
        req_id = _next_id()
        assert req_id not in self._pending
        if response_class != Void:
            result = gevent.event.AsyncResult()
            self._pending[req_id] = result

        try:
            req = rpc.Request()
            req.method = mdesc.name
            req.request = request.SerializeToString()

            packet = rpc.Packet()
            packet.type = rpc.REQUEST
            packet.identify = req_id
            packet.serialized = req.SerializeToString()
            self._send(packet.SerializeToString())
            
            if response_class == Void:
                return

            resp = response_class()
            resp_buff = result.get()
            resp.ParseFromString(resp_buff)
            done(resp)
        finally:
            try:
                self._pending.pop(req_id)
            except KeyError:
                pass

    def _send(self, buff):
        self._transport.send(buff)

